home *** CD-ROM | disk | FTP | other *** search
/ Programming a Multiplayer FPS in DirectX / Programming a Multiplayer FPS in DirectX (Companion CD).iso / Source / Chapter 9 / Engine / Network.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2004-10-01  |  17.1 KB  |  516 lines

  1. //-----------------------------------------------------------------------------
  2. // Network.h implementation.
  3. // Refer to the Network.h interface for more details.
  4. //
  5. // Programming a Multiplayer First Person Shooter in DirectX
  6. // Copyright (c) 2004 Vaughan Young
  7. //-----------------------------------------------------------------------------
  8. #include "Engine.h"
  9.  
  10. //-----------------------------------------------------------------------------
  11. // The network class constructor.
  12. //-----------------------------------------------------------------------------
  13. Network::Network( GUID guid, void (*HandleNetworkMessageFunction)( ReceivedMessage *msg ) )
  14. {
  15.     // Initialise the critical sections.
  16.     InitializeCriticalSection( &m_sessionCS );
  17.     InitializeCriticalSection( &m_playerCS );
  18.     InitializeCriticalSection( &m_messageCS );
  19.  
  20.     // Invalidate the DirectPlay peer interface and the device.
  21.     m_dpp = NULL;
  22.     m_device = NULL;
  23.  
  24.     // Store the application's GUID.
  25.     memcpy( &m_guid, &guid, sizeof( GUID ) );
  26.  
  27.     // Create the enumerated session list.
  28.     m_sessions = new LinkedList< SessionInfo >;
  29.  
  30.     // Create the player list.
  31.     m_players = new LinkedList< PlayerInfo >;
  32.  
  33.     // Create the network message list.
  34.     m_messages = new LinkedList< ReceivedMessage >;
  35.  
  36.     // Load the network settings.
  37.     Script *settings = new Script( "NetworkSettings.txt" );
  38.     if( settings->GetNumberData( "processing_time" ) == NULL )
  39.     {
  40.         m_port = 2509;
  41.         m_sendTimeOut = 100;
  42.         m_processingTime = 100;
  43.     }
  44.     else
  45.     {
  46.         m_port = *settings->GetNumberData( "port" );
  47.         m_sendTimeOut = *settings->GetNumberData( "send_time_out" );
  48.         m_processingTime = *settings->GetNumberData( "processing_time" );
  49.     }
  50.     SAFE_DELETE( settings );
  51.  
  52.     // Initially the network is not allowed to receive application specific messages.
  53.     m_receiveAllowed = false;
  54.  
  55.     // Set the network message handler.
  56.     HandleNetworkMessage = HandleNetworkMessageFunction;
  57.  
  58.     // Create and initialise the DirectPlay peer interface.
  59.     CoCreateInstance( CLSID_DirectPlay8Peer, NULL, CLSCTX_INPROC, IID_IDirectPlay8Peer, (void**)&m_dpp );
  60.     m_dpp->Initialize( (PVOID)this, NetworkMessageHandler, DPNINITIALIZE_HINT_LANSESSION );
  61.  
  62.     // Create the device address.
  63.     CoCreateInstance( CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC, IID_IDirectPlay8Address, (LPVOID*) &m_device );
  64.     m_device->SetSP( &CLSID_DP8SP_TCPIP );
  65.     m_device->AddComponent( DPNA_KEY_PORT, &m_port, sizeof(DWORD), DPNA_DATATYPE_DWORD );
  66. }
  67.  
  68. //-----------------------------------------------------------------------------
  69. // The network class destructor.
  70. //-----------------------------------------------------------------------------
  71. Network::~Network()
  72. {
  73.     // Save the network settings.
  74.     Script *settings = new Script( "NetworkSettings.txt" );
  75.     if( settings->GetNumberData( "processing_time" ) == NULL )
  76.     {
  77.         settings->AddVariable( "port", VARIABLE_NUMBER, &m_port );
  78.         settings->AddVariable( "send_time_out", VARIABLE_NUMBER, &m_sendTimeOut );
  79.         settings->AddVariable( "processing_time", VARIABLE_NUMBER, &m_processingTime );
  80.     }
  81.     else
  82.     {
  83.         settings->SetVariable( "port", &m_port );
  84.         settings->SetVariable( "send_time_out", &m_sendTimeOut );
  85.         settings->SetVariable( "processing_time", &m_processingTime );
  86.     }
  87.     settings->SaveScript();
  88.     SAFE_DELETE( settings );
  89.  
  90.     // Release the device address.
  91.     SAFE_RELEASE( m_device );
  92.  
  93.     // Close and release the DirectPlay peer interface.
  94.     if( m_dpp != NULL )
  95.         m_dpp->Close( DPNCLOSE_IMMEDIATE );
  96.     SAFE_RELEASE( m_dpp );
  97.  
  98.     // Destroy the enumerated session list.
  99.     SAFE_DELETE( m_sessions );
  100.  
  101.     // Destroy the player list.
  102.     SAFE_DELETE( m_players );
  103.  
  104.     // Destroy the network message list.
  105.     SAFE_DELETE( m_messages );
  106.  
  107.     // Delete the critical sections.
  108.     DeleteCriticalSection( &m_sessionCS );
  109.     DeleteCriticalSection( &m_playerCS );
  110.     DeleteCriticalSection( &m_messageCS );
  111. }
  112.  
  113. //-----------------------------------------------------------------------------
  114. // Updates the network object, allowing it to process messages.
  115. //-----------------------------------------------------------------------------
  116. void Network::Update()
  117. {
  118.     EnterCriticalSection( &m_messageCS );
  119.  
  120.     ReceivedMessage *message = m_messages->GetFirst();
  121.  
  122.     unsigned long endTime = timeGetTime() + m_processingTime;
  123.     while( endTime > timeGetTime() && message != NULL )
  124.     {
  125.         HandleNetworkMessage( message );
  126.         m_messages->Remove( &message );
  127.         message = m_messages->GetFirst();
  128.     }
  129.  
  130.     LeaveCriticalSection( &m_messageCS );
  131. }
  132.  
  133. //-----------------------------------------------------------------------------
  134. // Enumerates for sessions on the local network.
  135. //-----------------------------------------------------------------------------
  136. void Network::EnumerateSessions()
  137. {
  138.     // Empty the lists.
  139.     m_players->Empty();
  140.     m_messages->Empty();
  141.     m_sessions->Empty();
  142.  
  143.     // Prepare the application description.
  144.     DPN_APPLICATION_DESC description;
  145.     ZeroMemory( &description, sizeof( DPN_APPLICATION_DESC ) );
  146.     description.dwSize = sizeof( DPN_APPLICATION_DESC );
  147.     description.guidApplication = m_guid;
  148.  
  149.     // Enumerate sessions synchronously.
  150.     m_dpp->EnumHosts( &description, NULL, m_device, NULL, 0, 1, 0, 0, NULL, NULL, DPNENUMHOSTS_SYNC );
  151. }
  152.  
  153. //-----------------------------------------------------------------------------
  154. // Attempts to host a session.
  155. //-----------------------------------------------------------------------------
  156. bool Network::Host( char *name, char *session, int players, void *playerData, unsigned long dataSize )
  157. {
  158.     WCHAR wide[MAX_PATH];
  159.  
  160.     // Prepare and set the player information structure.
  161.     DPN_PLAYER_INFO player;
  162.     ZeroMemory( &player, sizeof( DPN_PLAYER_INFO ) );
  163.     player.dwSize = sizeof( DPN_PLAYER_INFO );
  164.     player.pvData = playerData;
  165.     player.dwDataSize = dataSize;
  166.     player.dwInfoFlags = DPNINFO_NAME | DPNINFO_DATA;
  167.     mbstowcs( wide, name, MAX_PATH );
  168.     player.pwszName = wide;
  169.     if( FAILED( m_dpp->SetPeerInfo( &player, NULL, NULL, DPNSETPEERINFO_SYNC ) ) )
  170.         return false;
  171.  
  172.     // Prepare the application description.
  173.     DPN_APPLICATION_DESC description;
  174.     ZeroMemory( &description, sizeof( DPN_APPLICATION_DESC ) );
  175.     description.dwSize = sizeof( DPN_APPLICATION_DESC );
  176.     description.guidApplication = m_guid;
  177.     description.dwMaxPlayers = players;
  178.     mbstowcs( wide, session, MAX_PATH );
  179.     description.pwszSessionName = wide;
  180.  
  181.     // Host the session.
  182.     if( FAILED( m_dpp->Host( &description, &m_device, 1, NULL, NULL, NULL, 0 ) ) )
  183.         return false;
  184.  
  185.     return true;
  186. }
  187.  
  188. //-----------------------------------------------------------------------------
  189. // Attempts to join a selected session from the enumerated session list.
  190. //-----------------------------------------------------------------------------
  191. bool Network::Join( char *name, int session, void *playerData, unsigned long dataSize )
  192. {
  193.     WCHAR wide[MAX_PATH];
  194.  
  195.     // Empty the player list and the newtork message.
  196.     m_players->Empty();
  197.     m_messages->Empty();
  198.  
  199.     // Ignore invalid sessions.
  200.     if( session < 0 )
  201.         return false;
  202.  
  203.     // Prepare and set the player information structure.
  204.     DPN_PLAYER_INFO player;
  205.     ZeroMemory( &player, sizeof( DPN_PLAYER_INFO ) );
  206.     player.dwSize = sizeof( DPN_PLAYER_INFO );
  207.     player.pvData = playerData;
  208.     player.dwDataSize = dataSize;
  209.     player.dwInfoFlags = DPNINFO_NAME | DPNINFO_DATA;
  210.     mbstowcs( wide, name, MAX_PATH );
  211.     player.pwszName = wide;
  212.     if( FAILED( m_dpp->SetPeerInfo( &player, NULL, NULL, DPNSETPEERINFO_SYNC ) ) )
  213.         return false;
  214.  
  215.     // Enter the sessions linked list critical section.
  216.     EnterCriticalSection( &m_sessionCS );
  217.  
  218.     // Find the host of the selected session.
  219.     m_sessions->Iterate( true );
  220.     for( int s = 0; s < session + 1; s++ )
  221.     {
  222.         if( m_sessions->Iterate() ==  NULL )
  223.         {
  224.             LeaveCriticalSection( &m_sessionCS );
  225.             return false;
  226.         }
  227.     }
  228.  
  229.     // Join the session.
  230.     if( FAILED( m_dpp->Connect( &m_sessions->GetCurrent()->description, m_sessions->GetCurrent()->address, m_device, NULL, NULL, NULL, 0, NULL, NULL, NULL, DPNCONNECT_SYNC ) ) )
  231.     {
  232.         LeaveCriticalSection( &m_sessionCS );
  233.         return false;
  234.     }
  235.     LeaveCriticalSection( &m_sessionCS );
  236.  
  237.     return true;
  238. }
  239.  
  240. //-----------------------------------------------------------------------------
  241. // Terminates the current session.
  242. //-----------------------------------------------------------------------------
  243. void Network::Terminate()
  244. {
  245.     // Only allow the host to terminate a session.
  246.     if( m_dpnidHost == m_dpnidLocal )
  247.         m_dpp->TerminateSession( NULL, 0, 0 );
  248.  
  249.     // Close the connection. This will also uninitialise the DirectPlay peer interface.
  250.     if( m_dpp != NULL )
  251.         m_dpp->Close( DPNCLOSE_IMMEDIATE );
  252.  
  253.     // Initialise the DirectPlay peer interface.
  254.     m_dpp->Initialize( (PVOID)this, NetworkMessageHandler, DPNINITIALIZE_HINT_LANSESSION );
  255. }
  256.  
  257. //-----------------------------------------------------------------------------
  258. // Sets the receive allowed flag.
  259. //-----------------------------------------------------------------------------
  260. void Network::SetReceiveAllowed( bool allowed )
  261. {
  262.     m_receiveAllowed = allowed;
  263. }
  264.  
  265. //-----------------------------------------------------------------------------
  266. // Returns the next iterated session from the enumerated session list.
  267. //-----------------------------------------------------------------------------
  268. SessionInfo *Network::GetNextSession( bool restart )
  269. {
  270.     EnterCriticalSection( &m_sessionCS );
  271.  
  272.     m_sessions->Iterate( restart );
  273.     if( restart == true )
  274.         m_sessions->Iterate();
  275.  
  276.     LeaveCriticalSection( &m_sessionCS );
  277.  
  278.     return m_sessions->GetCurrent();
  279. }
  280.  
  281. //-----------------------------------------------------------------------------
  282. // Returns a pointer to the player information structure of the given player.
  283. //-----------------------------------------------------------------------------
  284. PlayerInfo *Network::GetPlayer( DPNID dpnid )
  285. {
  286.     EnterCriticalSection( &m_playerCS );
  287.  
  288.     m_players->Iterate( true );
  289.     while( m_players->Iterate() )
  290.     {
  291.         if( m_players->GetCurrent()->dpnid == dpnid )
  292.         {
  293.             LeaveCriticalSection( &m_playerCS );
  294.  
  295.             return m_players->GetCurrent();
  296.         }
  297.     }
  298.  
  299.     LeaveCriticalSection( &m_playerCS );
  300.  
  301.     return NULL;
  302. }
  303.  
  304. //-----------------------------------------------------------------------------
  305. // Returns the local player's DirectPlay ID.
  306. //-----------------------------------------------------------------------------
  307. DPNID Network::GetLocalID()
  308. {
  309.     return m_dpnidLocal;
  310. }
  311.  
  312. //-----------------------------------------------------------------------------
  313. // Returns the host's DirectPlay ID.
  314. //-----------------------------------------------------------------------------
  315. DPNID Network::GetHostID()
  316. {
  317.     return m_dpnidHost;
  318. }
  319.  
  320. //-----------------------------------------------------------------------------
  321. // Indicates if the current network object is hosting or not.
  322. //-----------------------------------------------------------------------------
  323. bool Network::IsHost()
  324. {
  325.     if( m_dpnidHost == m_dpnidLocal )
  326.         return true;
  327.     else
  328.         return false;
  329. }
  330.  
  331. //-----------------------------------------------------------------------------
  332. // Sends a network message.
  333. //-----------------------------------------------------------------------------
  334. void Network::Send( void *data, long size, DPNID dpnid, long flags )
  335. {
  336.     DPNHANDLE hAsync;
  337.     DPN_BUFFER_DESC dpbd;
  338.  
  339.     if( ( dpbd.dwBufferSize = size ) == 0 )
  340.         return;
  341.     dpbd.pBufferData = (BYTE*)data;
  342.  
  343.     m_dpp->SendTo( dpnid, &dpbd, 1, m_sendTimeOut, NULL, &hAsync, flags | DPNSEND_NOCOMPLETE | DPNSEND_COALESCE );
  344. }
  345.  
  346. //-----------------------------------------------------------------------------
  347. // The internal network message handler.
  348. //-----------------------------------------------------------------------------
  349. HRESULT WINAPI Network::NetworkMessageHandler( PVOID context, DWORD msgid, PVOID data )
  350. {
  351.     // Get a pointer to the calling network object.
  352.     Network *network = (Network*)context;
  353.  
  354.     // Process the incoming message based on its type.
  355.     switch( msgid )
  356.     {
  357.         case DPN_MSGID_CREATE_PLAYER:
  358.         {
  359.             unsigned long size = 0;
  360.             DPN_PLAYER_INFO *info = NULL;
  361.             HRESULT hr = DPNERR_CONNECTING;
  362.             PDPNMSG_CREATE_PLAYER msgCreatePlayer = (PDPNMSG_CREATE_PLAYER)data;
  363.  
  364.             // Create a player information structure for the new player.
  365.             PlayerInfo *playerInfo = new PlayerInfo;
  366.             ZeroMemory( playerInfo, sizeof( PlayerInfo ) );
  367.             playerInfo->dpnid = msgCreatePlayer->dpnidPlayer;
  368.  
  369.             // Keep calling GetPeerInfo(), as it may still be trying to connect.
  370.             while( hr == DPNERR_CONNECTING )
  371.                 hr = network->m_dpp->GetPeerInfo( playerInfo->dpnid, info, &size, 0 );
  372.  
  373.             // Check if GetPeerInfo() has returned the size of the DPN_PLAYER_INFO structure.
  374.             if( hr == DPNERR_BUFFERTOOSMALL )
  375.             {
  376.                 info = (DPN_PLAYER_INFO*) new BYTE[size];
  377.                 ZeroMemory( info, size );
  378.                 info->dwSize = sizeof( DPN_PLAYER_INFO );
  379.  
  380.                 // Try again using the correct size.
  381.                 if( SUCCEEDED( network->m_dpp->GetPeerInfo( playerInfo->dpnid, info, &size, 0 ) ) )
  382.                 {
  383.                     // Store the name of the new player.
  384.                     playerInfo->name = new char[wcslen( info->pwszName ) + 1];
  385.                     ZeroMemory( playerInfo->name, wcslen( info->pwszName ) + 1 );
  386.                     wcstombs( playerInfo->name, info->pwszName, wcslen( info->pwszName ) );
  387.  
  388.                     // Store the player data.
  389.                     playerInfo->data = new BYTE[info->dwDataSize];
  390.                     memcpy( playerInfo->data, info->pvData, info->dwDataSize );
  391.                     playerInfo->size = info->dwDataSize;
  392.  
  393.                     // Store any local and host player details.
  394.                     if( info->dwPlayerFlags & DPNPLAYER_LOCAL )
  395.                         network->m_dpnidLocal = playerInfo->dpnid;
  396.                     if( info->dwPlayerFlags & DPNPLAYER_HOST )
  397.                         network->m_dpnidHost = playerInfo->dpnid;
  398.                 }
  399.  
  400.                 SAFE_DELETE_ARRAY( info );
  401.             }
  402.  
  403.             // Add the new player to the player list.
  404.             EnterCriticalSection( &network->m_playerCS );
  405.             network->m_players->Add( playerInfo );
  406.             LeaveCriticalSection( &network->m_playerCS );
  407.  
  408.             // If there is no network message handler, then break now.
  409.             if( network->HandleNetworkMessage == NULL )
  410.                 break;
  411.  
  412.             // Create a create player message.
  413.             ReceivedMessage *message = new ReceivedMessage;
  414.             message->msgid = MSGID_CREATE_PLAYER;
  415.             message->dpnid = playerInfo->dpnid;
  416.  
  417.             // Store the message so that it can be processed by the application later.
  418.             EnterCriticalSection( &network->m_messageCS );
  419.             network->m_messages->Add( message );
  420.             LeaveCriticalSection( &network->m_messageCS );
  421.  
  422.             break;
  423.         }
  424.  
  425.         case DPN_MSGID_DESTROY_PLAYER:
  426.         {
  427.             // Find the player to destroy and remove it from the player list.
  428.             EnterCriticalSection( &network->m_playerCS );
  429.             network->m_players->Iterate( true );
  430.             while( network->m_players->Iterate() )
  431.             {
  432.                 if( network->m_players->GetCurrent()->dpnid == ( (PDPNMSG_DESTROY_PLAYER)data )->dpnidPlayer )
  433.                 {
  434.                     network->m_players->Remove( (PlayerInfo**)network->m_players->GetCurrent() );
  435.                     break;
  436.                 }
  437.             }
  438.             LeaveCriticalSection( &network->m_playerCS );
  439.  
  440.             // If there is no network message handler, then break now.
  441.             if( network->HandleNetworkMessage == NULL )
  442.                 break;
  443.  
  444.             // Create a destroy player message.
  445.             ReceivedMessage *message = new ReceivedMessage;
  446.             message->msgid = MSGID_DESTROY_PLAYER;
  447.             message->dpnid = ( (PDPNMSG_DESTROY_PLAYER)data )->dpnidPlayer;
  448.  
  449.             // Store the message so that it can be processed by the application later.
  450.             EnterCriticalSection( &network->m_messageCS );
  451.             network->m_messages->Add( message );
  452.             LeaveCriticalSection( &network->m_messageCS );
  453.  
  454.             break;
  455.         }
  456.  
  457.         case DPN_MSGID_ENUM_HOSTS_RESPONSE:
  458.         {
  459.             PDPNMSG_ENUM_HOSTS_RESPONSE response = (PDPNMSG_ENUM_HOSTS_RESPONSE)data;
  460.  
  461.             // Create a session information structure for the new session.
  462.             SessionInfo *sessionInfo = new SessionInfo;
  463.             response->pAddressSender->Duplicate( &sessionInfo->address );
  464.             memcpy( &sessionInfo->description, response->pApplicationDescription, sizeof( DPN_APPLICATION_DESC ) );
  465.  
  466.             // Add the new session to the session list.
  467.             EnterCriticalSection( &network->m_sessionCS );
  468.             network->m_sessions->Add( sessionInfo );
  469.             LeaveCriticalSection( &network->m_sessionCS );
  470.  
  471.             break;
  472.         }
  473.  
  474.         case DPN_MSGID_RECEIVE:
  475.         {
  476.             // If there is no network message handler, then break now.
  477.             if( network->HandleNetworkMessage == NULL )
  478.                 break;
  479.  
  480.             // Check if the network is allowed to receive application specific messages.
  481.             if( network->m_receiveAllowed == false )
  482.                 break;
  483.  
  484.             // Create a receive message.
  485.             ReceivedMessage *message = new ReceivedMessage;
  486.             memcpy( message, ( (PDPNMSG_RECEIVE)data )->pReceiveData, ( (PDPNMSG_RECEIVE)data )->dwReceiveDataSize );
  487.  
  488.             // Store the message so that it can be processed by the application later.
  489.             EnterCriticalSection( &network->m_messageCS );
  490.             network->m_messages->Add( message );
  491.             LeaveCriticalSection( &network->m_messageCS );
  492.  
  493.             break;
  494.         }
  495.  
  496.         case DPN_MSGID_TERMINATE_SESSION:
  497.         {
  498.             // If there is no network message handler, then break now.
  499.             if( network->HandleNetworkMessage == NULL )
  500.                 break;
  501.  
  502.             // Create a terminate session message.
  503.             ReceivedMessage *message = new ReceivedMessage;
  504.             message->msgid = MSGID_TERMINATE_SESSION;
  505.  
  506.             // Store the message so that it can be processed by the application later.
  507.             EnterCriticalSection( &network->m_messageCS );
  508.             network->m_messages->Add( message );
  509.             LeaveCriticalSection( &network->m_messageCS );
  510.  
  511.             break;
  512.         }
  513.     }
  514.  
  515.     return S_OK;
  516. }